package com.youngjun.gateway.service.impl;

import com.youngjun.common.jpa.repository.support.BaseRepository;
import com.youngjun.gateway.pojo.domain.Route;
import com.youngjun.gateway.repository.RouteRepository;
import com.youngjun.gateway.service.RouteDefinitionService;
import com.youngjun.gateway.service.RouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author virgojun
 */
@Service
public class RouteServiceImpl implements RouteService {

    private ApplicationEventPublisher publisher;

    @Autowired
    private RouteRepository routeRepository;

    @Autowired
    private RouteDefinitionService routeDefinitionService;

    @Override
    public BaseRepository<Route, String> getRepository() {
        return routeRepository;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @Override
    public void run(String... args) {
        refresh();
    }

    @Override
    public void refresh() {
        List<Route> routes = findAll(false);
        routes.forEach(route -> {
            RouteDefinition definition=assembleRouteDefinition(route);
            routeDefinitionService.save(Mono.just(definition)).subscribe();
        });
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public List<Route> findAll(boolean useCache) {
        if(useCache){
            return routeDefinitionService.findRouteDefinitions()
                    .stream()
                    .map(item-> new Route().mapFrom(item))
                    .collect(Collectors.toList());
        }
        return findAll();
    }

    @Override
    public  Route save(Route route) {
        Route newRoute = getRepository().save(route);
        RouteDefinition definition=assembleRouteDefinition(newRoute);
        routeDefinitionService.save(Mono.just(definition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
        return newRoute;
    }

    @Override
    public void deleteById(String id) {
        routeDefinitionService.delete(Mono.just(id)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 路由数据转换公共方法
     * @param route
     * @return RouteDefinition
     */
    private RouteDefinition assembleRouteDefinition(Route route){
        RouteDefinition definition = new RouteDefinition();
        Map<String, String> predicateParams = new HashMap<>(8);
        PredicateDefinition predicate = new PredicateDefinition();
        FilterDefinition filterDefinition = new FilterDefinition();
        Map<String, String> filterParams = new HashMap<>(8);
        URI uri = null;
        if(route.getUri().startsWith("http")){
            //http地址
            uri = UriComponentsBuilder.fromHttpUrl(route.getUri()).build().toUri();
        }else{
            //注册中心
            uri = UriComponentsBuilder.fromUriString(route.getUri()).build().toUri();
        }
        definition.setId(route.getServiceId());
        // 名称是固定的，spring gateway会根据名称找对应的PredicateFactory
        predicate.setName("Path");
        predicateParams.put("pattern",route.getPredicates());
        predicate.setArgs(predicateParams);

        // 名称是固定的, 路径去前缀
        filterDefinition.setName("StripPrefix");
        filterParams.put(NameUtils.GENERATED_NAME_PREFIX, route.getFilters());
        filterDefinition.setArgs(filterParams);

        definition.setPredicates(Collections.singletonList(predicate));
        definition.setFilters(Collections.singletonList(filterDefinition));
        definition.setUri(uri);
        definition.setOrder(route.getSort());
        return definition;
    }

}
